/*
	GameServer.cpp

	This object is used to route verbs for one or more clients
	(c)1999 Palestar Development, Richard Lyle
*/

#include "Debug/Assert.h"
#include "Standard/Time.h"
#include "GameServer.h"
#include "GameClient.h"
#include "GameProfile.h"
#include "NounStar.h"
#include "NounJumpGate.h"
#include "TraitOrbit.h"
#include "VerbSpawn.h"

//---------------------------------------------------------------------------------------------------

const int MAX_CARGO_IN_STORAGE = 30;
const int MAX_SHIPS_IN_STORAGE = 8;
const int MAX_SPAWN_ATTEMPTS = 10;

//---------------------------------------------------------------------------------------------------

static bool GetKeyValue( const char * a_pData, const char * a_pKey, CharString & a_sValue )
{
	char * pValue = strstr( a_pData, a_pKey );
	if (! pValue )
		return false;
	
	char * pEqual = strchr( pValue, '=' );
	if (! pEqual )
		return false;
	pEqual += 1;

	while( *pEqual && *pEqual != ';' )
		a_sValue += *pEqual++;

	return true;
}

static int GetKeyIntegerValue( const char * a_pData, const char * a_pKey )
{
	CharString sValue;
	if (! GetKeyValue( a_pData, a_pKey, sValue ) )
		return 0;

	return atoi( sValue );
}

//---------------------------------------------------------------------------------------------------

IMPLEMENT_FACTORY( GameServer, WorldServer );

GameServer::GameServer()
{}

//---------------------------------------------------------------------------------------------------

dword GameServer::version() const
{
	return GameClient::sm_nVersion;
}

Profile * GameServer::createProfile()
{
	return new GameProfile();
}

bool GameServer::addPlayerProfile( dword nUserId, const char * pField, float fAmount )
{
	UserClientHash::iterator iClient = m_UserClientHash.find( nUserId );
	if ( iClient == m_UserClientHash.end() )
		return false;
    
	// if the player is local, then send out messages to all other players in this zone when they gain rank, badges, or medals
	ClientContextHash::iterator iContext = m_ClientContext.find( iClient->second );
	if ( iContext == m_ClientContext.end() )
		return false;

	ClientContext & context = iContext->second;
	GameProfile * pProfile = WidgetCast<GameProfile>( context.pProfile );
	if (! pProfile )
		return false;

	int nRank = pProfile->calculateRank();
	if(nRank < 0)
		nRank = 0;

	if ( nRank <= m_Context.maxRank )			// no rank gain for players over the maxrank for the server!
	{
		if (! WorldServer::addPlayerProfile( nUserId, pField, fAmount ) )
			return false;

		dword nBadges = pProfile->calculateBadges();
		dword nMedals = pProfile->calculateMedals( nBadges );

		pProfile->addStatistic( pField, fAmount );

		int nNewRank = pProfile->calculateRank();
		if ( nNewRank < 0 )
			nNewRank = 0;
		if ( nNewRank != nRank )
		{
			// player has changed rank, send message to all clients
			sendChat( CharString().format( "<color;ffffff>Comms: '%s' has been %s to '%s'", pProfile->name(), 
				nNewRank > nRank ? "promoted" : "demoted", GameProfile::rankText( nNewRank ) ) );
		}

		dword nNewBadges = pProfile->calculateBadges();
		if ( nNewBadges != nBadges )
		{
			dword nNewMedals = pProfile->calculateMedals(nNewBadges);

			for(int i=0;i<32;i++)
			{
				dword bit = 1 << i;
				if ( (nBadges & bit) == 0 && (nNewBadges & bit) != 0 )
				{
					sendChat( CharString().format( "<color;ffffff>Comms: '%s' has been awarded the '%s' badge", 
						pProfile->name(), GameProfile::badgeText( i ) ) );
				}

				if ( (nMedals & bit) == 0 && (nNewMedals & bit) != 0 )
				{
					sendChat( CharString().format( "<color;ffffff>Comms: '%s' has been awarded the '%s'", 
						pProfile->name(), GameProfile::medalText( i ) ) );
				}
			}
		}
	}

	return true;
}

bool GameServer::publishServer()
{
	if (! checkMetaServer() )
		return false;		// not connected to the metaserver!
	if (! m_Context.bPublish )
		return false;		// this server isn't published

	// update game information
	static const char * CONTEXT_TYPES[] = 
	{
		"Scenario",
		"Combat",
		"Clan",
		"Metaverse",
		"Tutorial"
	};

	lock();

	// if no name was supplied, use the name of this context
	GameContext * pContext = gameContext();
	if ( pContext == NULL )
	{
		unlock();
		return false;
	}
	ASSERT( pContext );

	// calculate the time and time remaining
	dword tick = pContext->tick();

	CharString sGameTime;
	sGameTime.format("%2.2u:%2.2u:%2.2u", tick / TICKS_PER_HOUR, 
		(tick / TICKS_PER_MINUTE) % 60, (tick / TICKS_PER_SECOND) % 60 );

	if ( pContext->timeLimit() > 0 )
	{
		dword remain = pContext->timeLimit() - tick;
		sGameTime += CharString().format(" - %2.2u:%2.2u:%2.2u", remain / TICKS_PER_HOUR, 
			(remain / TICKS_PER_MINUTE) % 60, (remain / TICKS_PER_SECOND) % 60 );
	}

	// update the meta server
	m_Server.flags = 0;			// MetaServer will set this to the same flags as our user account
	m_Server.type = MetaClient::GAME_SERVER;
	m_Server.name = m_Context.name; 
	m_Server.shortDescription = CharString().format("%s : %s", 
		CONTEXT_TYPES[ pContext->type() ], pContext->name() );

	CharString sGameInfo;
	sGameInfo.format( 
		"Time:       %s\n"
		"Balanced:   %s\n"
		"Clan:       %s\n"
		"Max Rank:   %s\n"
		"Version:    %6.6u\n", 
		sGameTime,
		m_Context.bBalanced ? "Yes" : "No",
		m_Context.bClan ? "Yes" : "No",
		GameProfile::rankText( m_Context.maxRank ),
		version() );


	m_Server.description = CharString().format("%s\n\n%s\n\n%s\n\n%s",
		m_Context.description, m_Server.shortDescription, pContext->description(), sGameInfo );

	m_Server.port = m_Context.port;
	m_Server.maxClients = m_Context.maxPlayers; //m_Context.maxClients;
	m_Server.clients = m_nPlayers; //clientCount();

	// store information about the worldContext into the data field of the server, so the website can display 
	// in-game information

	CharString sbData;
	sbData = CharString().format("FC=%d", pContext->teamCount() );
	for(int f=0;f<pContext->teamCount();f++)
	{
		const WorldContext::Team & team = pContext->teamN( f );
		sbData += CharString().format(";F%dID=%d", f, team.nTeamId );
		sbData += CharString().format(";F%dFID=%d", f, team.factionId );
		sbData += CharString().format(";F%dCID=%d", f, team.clanId );
		sbData += CharString().format(";F%dNAME=%s", f, team.name );
	}

	int planets = 0;		// planets
	int stars = 0;			// stars
	int gates = 0;			// gates

	sbData += CharString().format(";ZC=%d", pContext->zoneCount());
	for(int z=0;z<pContext->zoneCount();z++)
	{
		NodeZone * pZone = pContext->zone( z );
		ASSERT( pZone );

		if ( pZone->childCount() == 0 )
			continue;		// skip empty zones

		sbData += CharString().format(";Z%dID=%u", z, z );
		sbData += CharString().format(";Z%dPOS=%s", z, CharString( pZone->position() ) );
		
		for(int j=0;j<pZone->childCount();j++)
		{
			Noun * pNoun = WidgetCast<Noun>( pZone->child(j) );
			if ( pNoun == NULL )
				continue;

			if ( WidgetCast<NounPlanet>( pNoun ) )
			{
				NounPlanet * pPlanet = (NounPlanet *)pNoun;
				sbData += CharString().format(";P%dZONE=%d", planets, z );
				sbData += CharString().format(";P%dNAME=%s", planets, pPlanet->name() );
				sbData += CharString().format(";P%dPOS=%s", planets, CharString( pPlanet->position() ) );
				sbData += CharString().format(";P%dFLAG=%d", planets, pPlanet->flags() );
				sbData += CharString().format(";P%dFLEET=%d", planets, pPlanet->teamId() );
				sbData += CharString().format(";P%dPOP=%d", planets, ((int)pPlanet->population()) );
				sbData += CharString().format(";P%dUNIT=%d", planets, pPlanet->unitCount() );
				sbData += CharString().format(";P%dSTRU=%d", planets, pPlanet->structureCount() );
				sbData += CharString().format(";P%dSYS=%s", planets, pPlanet->starSystem() );

				Noun * pOrbiting = pPlanet->orbiting();
				if ( pOrbiting != NULL )
					sbData += CharString().format(";P%dORBIT=%s", planets, pOrbiting->name() );

				// get the clanId for the planet
				dword clanId = 0;
				if ( pContext->isTeamValid( pPlanet->teamId() ) )
					clanId = pContext->team( pPlanet->teamId() ).clanId;
				
				sbData += CharString().format(";P%dCLAN=%d", planets, clanId );

				// increment the planet count
				planets++;
			}
			else if ( WidgetCast<NounStar>( pNoun ) )
			{
				NounStar * pStar = (NounStar *)pNoun;

				sbData += CharString().format(";S%dZONE=%d", stars, z );
				sbData += CharString().format(";S%dNAME=%s", stars, pStar->name() );
				sbData += CharString().format(";S%dPOS=%s", stars, CharString( pStar->position() ) );
				sbData += CharString().format(";S%dFLEET=%d", stars, pStar->teamId() );

				Noun * pOrbiting = pStar->orbiting();
				if ( pOrbiting != NULL )
					sbData += CharString().format(";S%dORBIT=%s", stars, pOrbiting->name() );

				stars++;
			}
			else if ( WidgetCast<NounJumpGate>( pNoun ) )
			{
				NounJumpGate * pGate = (NounJumpGate *)pNoun;
				if ( pGate->duration() )
					continue;		// ignore inactive/temp gates (i.e. wormholes)

				sbData += CharString().format(";G%dZONE=%d", gates, z );
				sbData += CharString().format(";G%dNAME=%s", gates, pGate->name() );
				sbData += CharString().format(";G%dPOS=%s", gates, CharString( pGate->position() ) );

				Noun * pOrbiting = pGate->orbiting();
				if ( pOrbiting != NULL )
					sbData += CharString().format(";G%dORBIT=%s", gates, pOrbiting->name() );
				sbData += CharString().format(";G%dDEST=%s", gates, pGate->destination() );

				gates++;
			}
		}

	}

	sbData += CharString().format(";PLANETS=%d", planets );
	sbData += CharString().format(";STARS=%d", stars );
	sbData += CharString().format(";GATES=%d", gates );
	
	m_Server.data = sbData;

	TRACE( CharString().format("Registering server, server.data.length = %u", m_Server.data.length()) );

	unlock();

	// register this server
	m_MetaClient.registerServer( m_Server );

	// register our ProfilerServer
	MetaClient::Server profiler;

	m_ProfilerServer.lock();
	profiler.flags = 0;
	profiler.type = 7;
	profiler.name = m_Context.name;
	profiler.port = m_ProfilerServer.port();
	profiler.address = m_Context.address;
	profiler.maxClients = m_ProfilerServer.maxClients();
	profiler.clients = m_ProfilerServer.clientCount();
	profiler.shortDescription = CharString().format( "Profiler - Port:%u", profiler.port );
	profiler.description = CharString().format( "Port:%u\nAddress:%s\nClients:%u/%u", 
		profiler.port, profiler.address, profiler.clients, profiler.maxClients );
	m_ProfilerServer.unlock();

	m_MetaClient.registerServer( profiler );

	// connect jump gates to remote servers...
	Array< MetaClient::Server > servers;
	if ( m_MetaClient.getServers( m_Context.name, m_MetaClient.gameId(), MetaClient::GAME_SERVER, servers ) > 0 )
	{
		// connect our NounJumpGate objects to their remote servers...
		lock();
		
		for(int z=0;z<pContext->zoneCount();z++)
		{
			NodeZone * pZone = pContext->zone( z );
			ASSERT( pZone );

			for(int j=0;j<pZone->childCount();j++)
			{
				NounJumpGate * pGate = WidgetCast<NounJumpGate>( pZone->child(j) );
				if (! pGate )
					continue;
				if ( pGate->findDestination() )
					continue;		// destination is local, so skip this gate...
				if ( strlen( pGate->destination() ) == 0 )
					continue;		// no named destination, so this is a home gate..

				if ( pGate->isRemoteServer() )
				{
					// validate our current remote server...
					bool bServerFound = false;
					for(int k=0;k<servers.size() && !bServerFound;++k)
					{
						MetaClient::Server & server = servers[ k ];
						bServerFound = strcmp( server.address, pGate->address() ) == 0 && server.port == pGate->port();
					}

					if (! bServerFound )
					{
						log( CharString().format("Remote server %s:%d is down... shutting down gate %s!", 
							pGate->address(), pGate->port(), pGate->name()) );
						pGate->clearRemoteServer();
						// resend the gate to all clients
						pGate->resync();
					}
				}

				if (! pGate->isRemoteServer() )
				{
					// look for a server with our gate..
					bool bServerFound = false;
					for(int k=0;k<servers.size() && !bServerFound;++k)
					{
						MetaClient::Server & server = servers[k];

						int nGates = GetKeyIntegerValue( server.data, ";GATES=" );
						for(int i=0;i<nGates;++i)
						{
							CharString sGateKey;
							sGateKey.format( ";G%dNAME=", i );

							CharString sGateName;
							if (! GetKeyValue( server.data, sGateKey, sGateName ) )
								continue;
							if ( sGateName == pGate->destination() )
							{
								// found our remote gate, hook it up...
								log( CharString().format("Connecting gate %s to remote server at %s:%d!", 
									pGate->name(), server.address, server.port) );
								pGate->setRemoteServer( server.address, server.port );
								pGate->resync();

								bServerFound = true;
							}
						}
					}
				}
			}
		}
		unlock();
	}

	log( "Server registered..." );
	return true;
}

bool GameServer::prepWorld()
{
	GameContext * pGameWorld = WidgetCast<GameContext>( worldContext() );
	if (! pGameWorld )
		return false;

	pGameWorld->setGameUser( this );

	return WorldServer::prepWorld();
}

bool GameServer::cleanWorld()
{
	if (! m_pWorldContext.valid() )
		return false;

	Queue< Noun::Ref > remove;
	for(int i=0;i<m_pWorldContext->zoneCount();i++)
	{
		NodeZone * pZone = m_pWorldContext->zone( i );
		for(int j=0;j<pZone->childCount();j++)
		{
			Noun * pNoun = ((Noun *)pZone->child(j));
			if ( pNoun->isReplaced() )
			{
				// remove any replaced nouns...
				log( CharString().format( "Removing replaced noun with key of %s, class = %s",
					pNoun->key().string(), pNoun->factory()->className() ) );
				remove.push( pNoun );
			}
			else if ( WidgetCast<NounShip>( pNoun ) )
			{
				if ( ((NounShip *)pNoun)->userId() != 0 )
					remove.push( pNoun );
			}
			else if ( pNoun->nounType() == TYPE_OBJECT 
				|| pNoun->nounType() == TYPE_EFFECT )
			{
				// local objects and effects are removed such as projectiles and fighters
				remove.push( pNoun );
			}
		}
	}

	// detach the objects
	while( remove.valid() )
	{
		(*remove)->detachSelf();
		remove.next();
	}

	return WorldServer::cleanWorld();
}

bool GameServer::canJoinTeam( ClientContext & client, int nTeamId )
{
	if ( m_Context.bBalanced )
	{
		Hash< int, int > teamScore;
		for(int i=0;i<worldContext()->teamCount();i++)
			teamScore[ worldContext()->teamId( i ) ] = 0;

		for(i=0;i<clientCount();++i)
		{
			ClientContext & context = m_ClientContext[ GameServer::client(i) ];
			if ( context.nTeamId == 0 )
				continue;
			GameProfile * pProfile = WidgetCast<GameProfile>( context.pProfile );
			if (! pProfile )
				continue;
			int nRank = pProfile->calculateRank();
			if( nRank > 0 )
				teamScore[ context.nTeamId ] += nRank;
		}

		int nTopTeam = -1;
		int nTopTeamScore = 0;
		for(i=0;i<worldContext()->teamCount();i++)
		{
			int nTeamId = worldContext()->teamId( i );
			int nTeamScore = teamScore[ nTeamId ];

			if ( nTeamScore > nTopTeamScore )
			{
				nTopTeamScore = nTeamScore;
				nTopTeam = nTeamId;
			}
		}

		if ( nTeamId == nTopTeam )
			return false;
	}

	return WorldServer::canJoinTeam( client, nTeamId );
}

bool GameServer::canSpawn( ClientContext & context, Noun * pSpawnPoint, Noun * pSpawn, 
						  Array< Noun::Ref > & Storage )
{
	if (! WorldServer::canSpawn( context, pSpawnPoint, pSpawn, Storage ) )
		return false;
	NounShip * pSpawnShip = WidgetCast<NounShip>( pSpawn );
	if (! pSpawnShip )
		return false;			// we can only spawn ships currently!

	// use resources if the players ship is not coming from storage
	bool bUseResources = false;
	if ( Storage.search( pSpawn ) < 0 )
	{
		// new noun, we shall be using resources
		bUseResources = true;
		// check the number of ships in the player storage, do not spawn if player already has the maximum number of ships
		UserStorage * pStorage = getStorage( context.nUserId );
		if (! pStorage )
			return false;

		int nShipCount = 0;
		for(int i=0;i<pStorage->storageCount();++i)
		{
			const UserStorage::Storage & storage = pStorage->storage( i );
			if ( storage.nType == 1 )
				++nShipCount;
		}

		if ( nShipCount >= MAX_SHIPS_IN_STORAGE )
		{
			sendChat( context.nClientId, "SERVER: Maximum number of ships in storage!" );
			return false;		// no more ships in storage!
		}
	}

	// get the NounTemplate object for this ship
	NounTemplate * pNounTemplate = worldContext()->findNounTemplate( pSpawn );
	if (! pNounTemplate )
		return false;

	// get the player team information, if player doesn't have a valid team.. then try to select one based upon
	// the faction of the player's selected ship...
	if (! worldContext()->isTeamValid( context.nTeamId ) )
		return false;
	const WorldContext::Team & team = worldContext()->team( context.nTeamId );

	// count cargo items requested...a previous ship may be in this array as well, so ignore it...
	int nCargo = 0;
	for(int i=0;i<Storage.size();++i)
	{
		NounGame * pNounGame = WidgetCast<NounGame>( Storage[ i ] );
		if ( pNounGame && pNounGame->isCargo() )
			++nCargo;
	}

	if ( nCargo > pSpawnShip->availableCargo() )
	{
		sendChat( context.nClientId, "<color;ffffff>SERVER: Not enough cargo space!" );
		return false;
	}

	// allow admins to spawn any ship
	bool bIsAdmin = (context.MetaProfile.flags) != 0;
	// admins do not use resources when spawning
	if ( bIsAdmin )
		bUseResources = false;

	// allow developers to spawn any ship
	bool bIsDev = (context.MetaProfile.flags & MetaClient::DEVELOPER) != 0;
	// developers do not use resources when spawning
	if ( bIsDev )
		bUseResources = false;

	// check the player rank and badges
	GameProfile * pProfile = WidgetCast<GameProfile>( context.pProfile );
	if (! pProfile )
		return false;		// wrong profile type..

	int rank =  pProfile->calculateRank();
	if (rank < 0 )
		rank = 0;
	bool hasRank = Min<int>( rank, m_Context.maxRank ) >= pNounTemplate->rank();
	if ( (!hasRank && !bIsAdmin) )
	{
		sendChat( context.nClientId, "<color;ffffff>SERVER: Insufficient rank for ship!" );
		return false;
	}

	bool hasBadges = ( pProfile->calculateBadges() & pNounTemplate->flags()) == pNounTemplate->flags();
	if ( (!hasBadges && !bIsAdmin) )
	{
		sendChat( context.nClientId, "<color;ffffff>SERVER: Insufficient badges for ship!" );
		return false;
	}

	// check the spawn point
	if ( WidgetCast<NounJumpGate>( pSpawnPoint ) )
	{
		NounJumpGate * pGate = (NounJumpGate *)pSpawnPoint;
		if ( bUseResources && pGate->factionId() != team.factionId 
			&& pGate->factionId() != FACTION_NEUTRAL 
			&& pGate->factionId() != FACTION_UNKNOWN )
		{
			sendChat( context.nClientId, "<color;ffffff>SERVER: Jumpgate has been captured!" );
			return false;		// invalid gate for this player
		}
		if ( (pGate->factionId() == FACTION_NEUTRAL || pGate->factionId() == FACTION_UNKNOWN)
			&& pGate->findDestination() != NULL )
		{
			sendChat( context.nClientId, "<color;ffffff>SERVER: Jumpgate is not valid for spawning!" );
			return false;		// invalid gate for this player
		}
		if ( bUseResources && !pSpawnShip->enableGateSpawn() )
		{
			sendChat( context.nClientId, "<color;ffffff>SERVER: Ship must be built at shipyard!" );
			return false;		
		}
	}
	else if ( WidgetCast<NounPlanet>( pSpawnPoint ) )
	{
		NounPlanet * pPlanet = (NounPlanet *)pSpawnPoint;
		if ( pPlanet->factionId() != team.factionId )
		{
			sendChat( context.nClientId, "<color;ffffff>SERVER: Planet has been captured!" );
			return false;
		}
		if ( (pPlanet->flags() & NounPlanet::FLAG_HAS_SHIPYARD) == 0 )
		{
			sendChat( context.nClientId, "<color;ffffff>SERVER: Planet shipyard has been destroyed!" );
			return false;
		}
		if ( (pPlanet->flags() & NounPlanet::FLAG_BLOCKADE) != 0 )
		{
			sendChat( context.nClientId, "<color;ffffff>SERVER: Planet is under blockade!" );
			return false;
		}

		if ( bUseResources )
		{
			if ( pPlanet->technology() < NounShip::buildTechnology( pSpawnShip ) )
			{
				sendChat( context.nClientId, "<color;ffffff>SERVER: Planet technology too low to build this ship!" );
				return false;
			}
			if ( pPlanet->resources() < NounShip::buildCost( pSpawnShip ) )
			{
				sendChat( context.nClientId, "<color;ffffff>SERVER: Planet resources too low to build this ship!" );
				return false;
			}
		}
	}
	else
	{
		return false;		// unknown spawn point type
	}

	return true;
}

bool GameServer::onSpawn( ClientContext & context, Noun * pSpawnPoint, Noun * pSpawn, bool bNewSpawn )
{
	NounShip * pSpawnShip = WidgetCast<NounShip>( pSpawn );
	if (! pSpawnShip )
		return false;			// we can only spawn ships currently!

	// check the player rank and badges
	GameProfile * pProfile = WidgetCast<GameProfile>( context.pProfile );
	if (! pProfile )
		return false;		// wrong profile type..

	NodeZone * pZone = pSpawnPoint->zone();
	if (! pZone )
	{
		log( "ERROR: Spawn point has NULL zone!" );
		return false;
	}

	// get the NounTemplate object for this ship
	NounTemplate * pNounTemplate = worldContext()->findNounTemplate( pSpawn );
	if (! pNounTemplate )
	{
		log( "ERROR: Failed to find NounTemplate for spawning ship!" );
		return false;
	}
	
	// audit the ship against the template, add or remove gadgets as needed...
	NounShip * pShipTemplate = WidgetCast<NounShip>( pNounTemplate->noun() );
	if (! pShipTemplate )
	{
		log( "ERROR: NounTemplate noun is not a NounShip!" );
		return false;
	}

	if (! auditShip( pSpawnShip, pShipTemplate ) )
	{
		log( "ERROR: auditShip() failed!" );
		return false;
	}

	// Make sure the ship isn't jumping or anything...
	if ( pSpawnShip->jumpDrive() )
		pSpawnShip->jumpDrive()->clearJump();
	
	// if ship is destroyed, put the ship back to 5% hull...
	if ( pSpawnShip->destroyed()  )
		pSpawnShip->setDamage( pSpawnShip->maxDamage() * 0.95f );			// keep ship 95% damaged

	pSpawnShip->setHome( pSpawnPoint );
	pSpawnShip->setRank( pProfile->calculateRank() ); // keep ship real rank
	pSpawnShip->clearFlags( NounShip::FLAG_ALL );
	
	float fSpawnDistance = 200.0f;
	if ( WidgetCast<NounPlanet>( pSpawnPoint ) )
		fSpawnDistance = 200.0f;

	int nSpawnAttempts = 0;
	bool bSpawnValid = false;
	while(! bSpawnValid )
	{
		++nSpawnAttempts;

		// set the noun position in a random direction outside our spawn point radius
		Vector3 direction( RandomFloat(-1,1), 0.0f, RandomFloat(-1,1) );
		direction.normalize();

		Vector3 position( pSpawnPoint->position() );
		position += direction * ((pSpawnPoint->radius() + pSpawnShip->radius()) + fSpawnDistance );

		Array< GameContext::NounCollision > collisions;
		if (! pZone->proximityCheck( position, pSpawnShip->radius(), collisions ) )
		{
			bSpawnValid = true;

			float fHeading = atan2( direction.x, direction.z );
			pSpawnShip->setPosition( position );
			pSpawnShip->setOrientation( Vector3( 0.0f, fHeading, 0.0f ) );
			pSpawnShip->setHeading( fHeading );
		}
		else if ( nSpawnAttempts > MAX_SPAWN_ATTEMPTS )
		{
			sendChat( context.nClientId, "SERVER: Failed to find open spawn location!" );
			return false;
		}
	}

	// put ship into orbit if spawning from yard, break orbit if spawning from jump gate
	TraitOrbit * pOrbit = FindTrait<TraitOrbit>( pSpawnShip );
	if ( pOrbit != NULL )
	{
		if ( WidgetCast<NounPlanet>( pSpawnPoint ) )
		{
			Vector3 vDelta( pSpawnShip->position() - pSpawnPoint->position() );
			float fPosition = atan2( vDelta.x, vDelta.z );
			float fDistance = vDelta.magnitude();

			pSpawnShip->setVelocity( 0.0f );
			pOrbit->setOrbit( pSpawnPoint, fDistance, fPosition, 0.0f );
		}
		else
		{
			pSpawnShip->setVelocity( 5.0f );
			pOrbit->breakOrbit();
		}
	}

	// send out spawn message, makes jumpgates flash and resources get used
	Verb::Ref( new VerbSpawn( pSpawnPoint, pSpawnShip, bNewSpawn) );

	return true;
}

void GameServer::onDeleteClient( ClientContext & client )
{
	if ( client.nLogoffTime == 0 )
		client.nLogoffTime = Time::seconds();

	// update the time played
	addPlayerProfile( client.nUserId, "Time Played", client.nLogoffTime - client.nLoginTime );
	
	// send final score for player...
	GameProfile * pProfile = WidgetCast<GameProfile>( client.pProfile );
	if ( pProfile )
	{
		float fPrestige = pProfile->calculatePrestige();
		if (! m_MetaClient.updateScore( client.nUserId, fPrestige, GameProfile::rankText( pProfile->getRank( fPrestige ) ) ) )
			log( CharString().format( "Failed to update score for user %u", client.nUserId ) );
	}

	// call the base class so it can update the player queue..
	WorldServer::onDeleteClient( client );
}

bool GameServer::onStorageLoaded( UserStorage * a_pStorage )
{
	// revalidate the object types stored...
	for( int i=0;i<a_pStorage->storageCount();++i)
	{
		Storage & storage = a_pStorage->getStorage( i );
		Widget::Ref pUncasted = storage.instance.unwrap();
		if ( WidgetCast<NounCargo>( pUncasted ) != NULL )
		{
			if ( storage.nType != 0 )
			{
				log( CharString().format("ERROR: Found NounCargo storage with wrong type of %u!", storage.nType) );
				storage.nType = 0;
			}
		}
		else if ( WidgetCast<NounShip>( pUncasted ) != NULL )
		{
			if ( storage.nType != 1 )
			{
				log( CharString().format("ERROR: Found NounShip storage with wrong type of %u!", storage.nType) );
				storage.nType = 1;
			}
		}
		else
		{
			log( "ERROR: Unknown storage class type!" );
			storage.nType = 0xffffffff;
		}
	}

	return true;
}

//---------------------------------------------------------------------------------------------------

void GameServer::onAttachNoun( Noun * pNoun )
{
	// remove ship and cargo from storage
	if ( WidgetCast<NounShip>( pNoun ) )
	{
		NounShip * pSelf = (NounShip *)pNoun;
		ASSERT( pSelf );

		if ( pSelf->userId() != 0 )
			saveStorage( pSelf );
	}

	WorldServer::onAttachNoun( pNoun );
}

void GameServer::onDetachNoun( Noun * pNoun )
{
	// must get the faction ID before let it get detached from the world...
	dword nFactionId = pNoun->factionId();
	// grab a reference to the object so it doesn't delete while in this function..
	Noun::Ref pNounRef = pNoun;

	// NOTE: We call the base class FIRST so the message to detach the noun will be sent before we send 
	// the player their storage object (which has the same ID) that will cause problems with
	// detaching the ship from the GameContext
	WorldServer::onDetachNoun( pNoun );

	// add items to player storage
	if ( WidgetCast<NounShip>( pNoun ) != NULL )
	{
		NounShip * pSelf = (NounShip *)pNoun;
		ASSERT( pSelf );

		if ( pSelf->userId() != 0 )
		{
			dword nUserId = pSelf->userId();

			dword nClientId = getClientId( nUserId );
			ClientContext & client = m_ClientContext[ nClientId ];
			if (! client.bServerTransfer )
			{
				// clear the players ship, so they can select a new ship
				setClientSelf( 0, nClientId, NULL_WIDGET );

				// if ship is destroyed, add resources lost..
				if ( pSelf->destroyed()  )
				{
					// ship is destroyed, add resources lost
					float fScale = pSelf->rank() * 0.025f;
					float fResourcesLost = fScale * NounShip::buildCost( pSelf );

					addProfile( nUserId, RESOURCES_LOST, fResourcesLost );
				}

				bool bStorageFull = false;

				int nCargoCount = getStorageCargoCount( nUserId );
				for(int i=pSelf->childCount()-1;i>=0;i--)
				{
					NounCargo * pCargo = WidgetCast<NounCargo>( pSelf->child(i) );
					if ( pCargo == NULL )
						continue;
					
					if ( nCargoCount < MAX_CARGO_IN_STORAGE )
					{
						Storage storage;
						storage.nType = 0;
						storage.nFactionId = nFactionId;
						storage.instance.wrap( pCargo, 0 );
						// add cargo item to storage
						addStorage( 0, nUserId, pCargo->key(), storage );
						++nCargoCount;

						// detach the cargo from the ship
						pCargo->setSyncronized( false );
						pCargo->detachSelf();
					}
					else
						bStorageFull = true;
				}

				if ( bStorageFull )
					sendUserChat( nUserId, "<color;ffffff>Cargo: Storage is full, cargo remains on ship!" );

				// lastly, add the ship to storage
				Storage storage;
				storage.nType = 1;
				storage.nFactionId = nFactionId;
				storage.pLocation = NULL; //pSelf->home();
				storage.instance.wrap( pSelf, 0 );

				addStorage( 0, nUserId, pSelf->key(), storage );
			}
		}
	}
}

void GameServer::onCondition( int nConditionId )
{
	WorldContext * pContext = worldContext();
	ASSERT( pContext && pContext->isConditionValid( nConditionId ) );

	// get the condition
	const WorldContext::Condition & condition = pContext->condition( nConditionId );

	// generate the end message for all clients
	CharString sResult;
	sResult += condition.message;
	sResult += "\n\n";

	// get the prestige for all players
	Hash< dword, float > playerPrestige;
	for(int i=0;i<clientCount();i++)
	{
		ClientContext & context = m_ClientContext[ client(i) ];
		GameProfile * pProfile = WidgetCast<GameProfile>( context.pProfile );
		if (! pProfile )
			continue;		// client not logged in...

		playerPrestige[ i ] = pProfile->calculatePrestige();
	}

	// sort the players by rank
	Array< int > ranking;
	for(i=0;i<clientCount();i++)
	{
		// get the players prestige level
		float prestige = playerPrestige[ i ];

		bool inserted = false;
		for(int j=0;j<ranking.size();j++)
			if ( prestige > playerPrestige[ ranking[j] ] )
			{
				ranking.insert( j, i );
				inserted = true;
				break;
			}

		if ( !inserted )
			ranking.push( i );		// add client to the end of the list
	}

	sResult += "<color;ffffffff><X;300>Prestige<X;400>Bonus\n";
	for(i=0;i<ranking.size();i++)
	{
		ClientContext & context = m_ClientContext[ client(i) ];
		if (! pContext->isTeamValid( context.nTeamId ) )
			continue;

		GameProfile * pProfile = WidgetCast<GameProfile>( context.pProfile );
		if (! pProfile )
			continue;
		float fPrestige = pProfile->calculatePrestige();
		int nRank = pProfile->calculateRank();
		if(nRank < 0)
			nRank = 0;

		float fBonus = 0;
		for(int j=0;j<condition.objectives.size();j++)
			if ( pContext->checkObjective( condition.objectives[j] ) && condition.objectives[j].factionId == context.nFactionId )
				fBonus += 1.0f;

		// add bonus prestige to player
		addProfile( pProfile->userId(), BONUS_PRESTIGE, fBonus );

		// add player to result text
		sResult += CharString().format("%s %s<X;300>%.1f<X;400>+%.1f\n", 
			GameProfile::rankText( nRank ), pProfile->name(), fPrestige, fBonus );
	}

	// get the pointer to the next context before we call contextEnd() which will release our current context.
	WorldContext::Link pNextContext = condition.next;
	// end the old context
	contextEnd( 0, sResult );

	// attempt to load the next WorldContext object
	if ( pNextContext.valid() )
		contextInitialize( 0, WidgetWrap( pNextContext, FF_TRANSMIT ) );
	else
		m_bStopServer = true;	// no valid context, stop the server!
}

bool GameServer::saveStorage( Noun * a_pSelf )
{
	if ( m_Context.bPersistant && WidgetCast<NounShip>( a_pSelf ) != NULL && a_pSelf->userId() != 0 )
	{
		dword nUserId = a_pSelf->userId();
		UserStorageHash::Iterator iStorage = m_UserStorage.find( nUserId );
		if (! iStorage.valid() )
			return false;

		UserStorage::Ref pStorage = *iStorage;

		Storage active;
		active.nType = 1;
		active.instance.wrap( a_pSelf );
		active.nFactionId = a_pSelf->factionId();

		pStorage->add( a_pSelf->key(), active );
		log( CharString().format("Saving active user %u storage, count = %d, bytes = %d", 
			nUserId, pStorage->storageCount(), pStorage->bytes())  );

		if (! pStorage->save( false ) )
			log( CharString().format("ERROR: Failed to save user %u storage", nUserId) );
	}

	return true;
}

//---------------------------------------------------------------------------------------------------

void GameServer::onKill( Noun * pWho, Noun *pDead )
{
	addProfile( pWho, KILLS, 1.0f );
}

void GameServer::onDestroyed( NounShip * pWho, Noun * pKiller )
{
	addProfile( pWho, KILLED, 1.0f );
}

void GameServer::onDamageShip( Noun * pWho, float damage, Noun *pTarget )
{
	float fScale = 1.0f;
	if( WidgetCast<NounShip>( pWho ) != NULL 
		&& WidgetCast<NounShip>( pTarget ) != NULL )
	{
		// if player damaged an AI, scale it down
		if(pWho->userId() != 0 && pTarget->userId() == 0)
		{
			// players rank
			// ok give players lower than rank 3 full
			// 4-8 50%
			// greater that 8 25%			
			int nRank = ((NounShip *)pWho)->rank();
			if ( nRank < 1 )
				nRank = 1;

			if( nRank <= 3) // full value
				fScale = 1.0f;
			else if(nRank <= 8)
				fScale = 0.5f;
			else 
				fScale = 0.25f;
		}
	}
	addProfile( pWho, SHIPS_DAMAGED, damage * fScale );
}

void GameServer::onCaptureShip( Noun * pWho, NounShip * pCaptured )
{
	float fScale = 1.0f;

	// get the player credit for capping the ship
	if( WidgetCast<NounShip>( pWho ) != NULL 
		&& WidgetCast<NounShip>( pCaptured ) != NULL )
	{
		// if player captured an AI (can this even happen?)
		if(pWho->userId() != 0 && pCaptured->userId() == 0)
		{
			// players rank
			// ok give players lower than rank 3 full
			// 4-8 50%
			// greater that 8 25%			
			int nRank = ((NounShip *)pWho)->rank();
			if ( nRank < 1 )
				nRank = 1;

			if(nRank <= 3) // full value
				fScale = 1.0f;
			else if(nRank <= 8)
				fScale = 0.5f;
			else
				fScale = 0.25f;
		}
	}
	addProfile( pWho, SHIPS_CAPTURED, 1.0f * fScale );
}

void GameServer::onFriendlyFire( Noun * pWho, float damage )
{
	addProfile( pWho, FRIENDLY_FIRE, damage );
}

void GameServer::onPlanetsDamaged( Noun * pWho, float damage )
{
	addProfile( pWho, PLANETS_DAMAGED, damage );
}

void GameServer::onPlanetsCaptured( Noun * pWho, float amount )
{
	addProfile( pWho, PLANETS_CAPTURED, amount );
}

void GameServer::onRepair( Noun * pWho, float repair )
{
	addProfile( pWho, REPAIR, repair );
}

void GameServer::onConstruct( Noun * pWho, float construct )
{
	addProfile( pWho, CONSTRUCTION, construct );
}

void GameServer::onKamikaze( Noun * pWho, float damage )
{
	addProfile( pWho, KAMIKAZE, damage );
}

void GameServer::onSelfDestruct( NounShip * pWho )
{
	addProfile( pWho, SELF_DESTRUCTS, 1.0f );
}

void GameServer::onJump( Noun * pWho )
{
	addProfile( pWho, JUMPS, 1.0f );
}

void GameServer::onCollidePlanet( Noun * pWho )
{
	addProfile( pWho, PLANET_COLLISIONS, 1.0f );
}

void GameServer::onResourcesLost( Noun * pWho, float amount )
{
	addProfile( pWho, RESOURCES_LOST, amount );
}

void GameServer::onScout( Noun * pWho, float points )
{
	addProfile( pWho, SCOUT, points );
}

void GameServer::onBonusPrestige( Noun * pWho, float bonus )
{
	addProfile( pWho, BONUS_PRESTIGE, bonus );
}

void GameServer::onCredits( Noun * pWho, int credits )
{
	//addProfile( pWho, CREDITS, credits );
}

void GameServer::onOrderReceived( NounShip * pWho, NounShip::Order order, Noun * pTarget, Noun * pFrom )
{}

//---------------------------------------------------------------------------------------------------

bool GameServer::addProfile( dword nUserId, Statistics nStat, float fValue )
{
	return addPlayerProfile( nUserId, GameProfile::statisticName( nStat ), fValue );
}

bool GameServer::addProfile( Noun * pTarget,Statistics nStat, float fValue )
{
	if (! pTarget || !pTarget->userId() )
		return false;
	return addPlayerProfile( pTarget->userId(), GameProfile::statisticName( nStat ), fValue );
}

int GameServer::getStorageCargoCount( dword nUserId )
{
	UserStorage * pStorage = getStorage( nUserId );
	if (! pStorage )
		return 0;

	int nCargoCount = 0;
	for(int i=0;i<pStorage->storageCount();++i)
	{
		if ( pStorage->storage( i ).nType == 0 )
			++nCargoCount;
	}

	return nCargoCount;
}

bool GameServer::auditShip( NounShip * a_pShip, NounShip * a_pShipTemplate )
{
	if (! a_pShipTemplate || !a_pShip )
		return false;

	for( int j=0,i=0;i < a_pShipTemplate->childCount(); ++i )
	{
		NounGadget * pGadgetT = WidgetCast<NounGadget>( a_pShipTemplate->child(i) );
		if ( pGadgetT != NULL )
		{
			NounGadget * pGadget = NULL;
			for( ;!pGadget && j < a_pShip->childCount(); ++j )
				pGadget = WidgetCast<NounGadget>( a_pShip->child(j) );

			Array< NounGadget * > upgrades;
			pGadgetT->getUpgrades( upgrades );

			bool bIsValid = pGadget != NULL && pGadget->factory()->classKey() == pGadgetT->factory()->classKey();
			if (! bIsValid )
			{
				// check the upgrade paths, it might be one of those gadgets.
				for(int k=0;k<upgrades.size();++k)
				{
					NounGadget * pUpgrade = upgrades[k];
					if ( pUpgrade != NULL && pUpgrade->factory()->classKey() == pGadget->factory()->classKey() )
					{
						pGadgetT = pUpgrade;
						bIsValid = true;
						break;
					}
				}
			}

			if ( bIsValid )
			{	
				// correct gadget... copy updates from the template.
				pGadget->setPosition( pGadgetT->position() );
				pGadget->setOrientation( pGadgetT->orientation() );
				pGadget->setName( pGadget->name() );
				pGadget->setNounContext( pGadgetT->nounContext() );
				pGadget->setFlags( pGadgetT->flags() );
			}
			else
			{
				pGadget = (NounGadget *)pGadgetT->copy();
				pGadget->setup();

				// gadgets don't match, insert a copy into the ship being validated..
				log( CharString().format( "AuditShip: Adding gadget %s", pGadget->name() ) );

				int n = j - 1;
				if ( n < a_pShip->childCount() )
					a_pShip->insertNode( n, pGadget );
				else
					a_pShip->attachNode( pGadget );
			}
		}
	}

	// remove extra child gadgets that were not found in the template...
	while( j < a_pShip->childCount() )
	{
		NounGadget * pGadget = WidgetCast<NounGadget>( a_pShip->child(j) );
		if ( pGadget != NULL )
		{
			log( CharString().format( "AuditShip: Removing gadget %s", pGadget->name() ) );
			a_pShip->detachNode( j );
		}
		else
			j += 1;
	}

	return true;
}
	
//----------------------------------------------------------------------------
// EOF
